package org.axed.user.client;

import java.util.ArrayList;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Element;

import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;

/**
 * Handles UserInput events and sends out Document alteration events.
 */
public class UserInput extends InputBase implements AxedListener  {
	public final static int TABSTOP = 2;

	/**	
	 * Constructor.
	 */
	public UserInput(AxedArea axed) {
		super(axed);
	}

	/**
	 * Handles events from the ace area.
	 */
	public void onAxedEvent(AxedEvent event) {
		super.onAxedEvent(event);

		switch (event.type) {
		case AxedEvent.KEY_PRESS:
			{
			AxedEvent.KeyPress ev = event.getKeyPress();
			if (ev.ctrlKey) {
				handleCtrlKey(ev.keycode, ev.shiftKey);
				return;
			}

			if (ev.keycode < 0) { // some special char.
				handleKey(-ev.keycode, ev.shiftKey);
				return;
			} 
			
			// its a normal character to insert.
			if (select) {
				// delete a selection if selected
				lastAtomicID = axed.createAtomicID();
				deleteSelection(lastAtomicID);
			}
			newChar(ev.ch);
			break;
			}	
		}
	}

	/**
	 * Deletes a character before the cursor (backspace)
	 */
	protected void backspace() {
		if (cursor.isPos1()) {
			return;
		}
		if (cursor.col >= 1) {
			cursor.col--;
			char ch = axed.getChar(cursor);
			if ((lastAtomicID == 0) ||
				(lastEventType == EDIT_BACKSPACESPACE && ch != ' ') || 
				(lastEventType != EDIT_BACKSPACE && lastEventType != EDIT_BACKSPACESPACE)) {
				lastAtomicID = axed.createAtomicID();
			}
			axed.removeChar(lastAtomicID, cursor, InputCatcher.KEY_BACKSPACE);
			lastEventType = (ch == ' ') ? EDIT_BACKSPACESPACE : EDIT_BACKSPACE;
		} else { 
			assert cursor.line > 0 && cursor.col == 0;
			cursor.col = axed.getLineLength(cursor.line - 1);
			cursor.line--;
			lastAtomicID = axed.createAtomicID();
			axed.joinLine(lastAtomicID, cursor.line);
			lastEventType = EDIT_BACKSPACE;
		}
	}

	/**
	 * Deletes a character after the cursor (del key pressed).
	 */
	protected void del() {
		if (cursor.isEol()) {
			if (cursor.isLastLine()) {
				return;
			}
			lastAtomicID = axed.createAtomicID();
			axed.joinLine(lastAtomicID, cursor.line);
			return;
		}
		char ch = axed.getChar(cursor);
		if ((lastAtomicID == 0) ||
			(lastEventType == EDIT_DELSPACE && ch != ' ') || 
			(lastEventType != EDIT_DEL && lastEventType != EDIT_DELSPACE)) {
			lastAtomicID = axed.createAtomicID();
		}
		lastEventType = (ch == ' ') ? EDIT_DELSPACE : EDIT_DEL;
		axed.removeChar(lastAtomicID, cursor, InputCatcher.KEY_DEL);
	}	
	
	/**
	 * Adds a character at cursor.
	 */
	private void newChar(char ch) {
		if ((lastAtomicID == 0) ||
			(lastEventType == EDIT_INSERTSPACE && ch != ' ') || 
			(lastEventType != EDIT_INSERT && lastEventType != EDIT_INSERTSPACE)) {
			lastAtomicID = axed.createAtomicID();
		}
		axed.insertChar(lastAtomicID, cursor, ch);
		cursor.col++;
		cursor.retainLeft = -1;
		lastEventType = (ch == ' ') ? EDIT_INSERTSPACE : EDIT_INSERT;
		fireCursorMoved();
	}

// privates:

	/**
	 * common logic to all cursor moves.
	 */
	private void moveCursor(boolean shift) {
		if (shift) {
			if (!select) {
				selection.setAnchor1(cursor);
				select = true;
			}
		} else {
			if (select) {
				selection.unset();
				select = false;
			}
		}
	}

	/**
	 * common expost-logic to all cursor moves.
	 */
	private void moveCursor2(boolean shift) {
		if (shift) {
			selection.setAnchor2(cursor);
			selection.setVisible(true);
			selection.commit();
		}
		fireCursorMoved();
		lastAtomicID = 0;
		lastEventType = EDIT_MOVE;
	}

	/**
	 * Handles spezial keys.
	 *
	 * @param shiftKey   True if the shift key is pressed.
	 */
	private void handleKey(int keycode, boolean shift) {
		switch (keycode) {
		case InputCatcher.KEY_BACKSPACE: 
			if (select) {
				lastAtomicID = axed.createAtomicID();
				deleteSelection(lastAtomicID);
			} else {
				backspace();
			}
			axed.moveCursor(cursor, AxedEvent.MOVETYPE_FOLLOWUP);
			break;
		case InputCatcher.KEY_TAB:
			for(int i = 0; i < TABSTOP; i++) {
				newChar(' ');
			}
			break;
		case InputCatcher.KEY_RETURN: 
			lastAtomicID = axed.createAtomicID();
			if (select) {
				deleteSelection(lastAtomicID);
			}			
			newline(lastAtomicID);
			axed.moveCursor(cursor, AxedEvent.MOVETYPE_FOLLOWUP);
			break;
		case InputCatcher.KEY_PAGEUP:
			moveCursor(shift);
			cursor.movePages(-1);
			axed.setScrollTop(axed.getScrollTop() - axed.getClientHeight());
			moveCursor2(shift);
			break;
		case InputCatcher.KEY_PAGEDOWN:
			moveCursor(shift);
			cursor.movePages(1);
			axed.setScrollTop(axed.getScrollTop() + axed.getClientHeight());
			moveCursor2(shift);
			break;
		case InputCatcher.KEY_END: 
			moveCursor(shift);
			cursor.moveLineEnd(true);
			moveCursor2(shift);
			break;
		case InputCatcher.KEY_POS1: 
			moveCursor(shift);
			cursor.moveLineStart(true);
			moveCursor2(shift);
			break;
		case InputCatcher.KEY_LEFT_ARROW:
			moveCursor(shift);
			cursor.moveLeft(1, true);
			moveCursor2(shift);
			break;
		case InputCatcher.KEY_UP_ARROW: 
			moveCursor(shift);
			cursor.moveUp(1, true);
			moveCursor2(shift);
			break;
		case InputCatcher.KEY_RIGHT_ARROW:
			moveCursor(shift);
			cursor.moveRight(1, true);
			moveCursor2(shift);
			break;
		case InputCatcher.KEY_DOWN_ARROW:
			moveCursor(shift);
			cursor.moveDown(1, true);
			moveCursor2(shift);
			break;
		case InputCatcher.KEY_INSERT:
			break;
		case InputCatcher.KEY_DEL:
			if (select) {
				lastAtomicID = axed.createAtomicID();
				deleteSelection(lastAtomicID);
			} else {
				del();
			}
			break;
		}
	}

	/**	
	 * Adds a new line at cursor.
	 */
	private void newline(int atomicID) {
		axed.splitLine(atomicID, cursor);
		cursor.col = 0;
		cursor.line++;
		cursor.retainLeft = -1;
		lastEventType = EDIT_NEWLINE;
	}

	/**
	 * Handles keys for pressed ctrl.
	 *
	 * @param shiftKey   True if the shift key is pressed.
	 */
	private void handleCtrlKey(int keycode, boolean shift) {
		switch (keycode) {
		case 'a' :
		case 'A' :
			axed.setSelection(0, 0, axed.getLineCount() - 1, axed.getLineLength(axed.getLineCount() - 1));
			break;
		case 'v' :
		case 'V' :
			axed.inputCatcher.paste = true;
			break;
		case 'c' :
		case 'C' :
			axed.inputCatcher.setText(getSelection());
			axed.inputCatcher.selectAll();
			break;
		case 'x' :
		case 'X' :
			axed.inputCatcher.setText(getSelection());
			axed.inputCatcher.selectAll();
			axed.cut();
			break;
		case 'y' :
		case 'Y' :
			axed.redo();
			break;
		case 'z' :
		case 'Z' :
			axed.undo();
			break;
		case '1' :
			Window.alert("This is Axed version " + AxedArea.VERSION);
			break;
		case -InputCatcher.KEY_LEFT_ARROW:
			moveCursor(shift);
			cursor.movePrevWord(1, false, true);
			moveCursor2(shift);
			break;
		case -InputCatcher.KEY_RIGHT_ARROW:
			moveCursor(shift);
			cursor.moveNextWord(1, false, false);
			moveCursor2(shift);
			break;
		}
		// let the borwser have the stuff if ctrl key is pressed.
	}
}
